<?php
/**
 * XmuSlh, web platform of Student Association Federation of Xiamen University
 * 
 * Rewritting one or some pages of StatusNet. 
 * To get the original version, please visit <http://status.net/>.
 * 
 * StatusNet, the distributed open-source microblogging tool
 * 
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * A router class to serve the router mapping. 
 * Direct the route to the relative class.
 * 
 * 路径映射, 将http://../groups等的URL映射到相应的Action, 并且对参数进行解释.
 * 
 * @author		ytukie <ytukie@gmail.com>
 * @version		0.12
 * 
 * 0.11) Adds the 'article' action; Enables the cache?
 * 		 Adds the 'newarticle' action.
 * 0.12) Adds the 'newfile' action.
 * 		 Adds the 'getfile' action.
 * 0.13) Adds the 'login' test action.
 * 		 Adds the 'logout' test action.
 * 0.14) Adds the 'getjson' test action.
 * 		 Adds the 'parsejson' test action.
 * 0.15) Adds the 'css' test action.
 * 0.2)  Adds the 'testactionframework' test action.
 * 0.21) Adds the 'adddata' action.
 * 0.22) Adds the 'addprofileuser' and 'addassociationuser' action.
 * 0.23) Adds the 'showprofileuser' action.
 * 		 Adds the 'deleteprofileuser' action.
 * 0.24) Adds the 'showassociationuser' action,
 * 		 Adds the 'editassociationuser', 'deleteassociationuser' action,
 * 		 Adds the 'editavatar' action.
 * 0.25) Adds the 'login', 'logout' action.
 * 		 Adds the 'adddata' action.
 * 		 Adds the 'showdata' action.
 * 		 Adds the 'getdata' action.
 * 0.26) Adds the 'deletedata' action.
 * 0.27) Adds the 'addphotoset' action.
 * 0.28) Adds the 'addphoto', 'deletephoto' action.
 * 		 Adds the 'showphotoset', 'deletephotoset', 'editphotoset' action.
 * 		 Adds the 'photoset' action.
 * 0.29) Adds the 'addphotonews' action.
 * 0.3)  Adds the 'uplaodpicturejson' action.
 * 0.31) Adds the 'addmicronews' action.
 * 		 Adds the 'micronews', 'deletemicronews' action.
 * 0.32) Adds the 'addeventnews', 'deleteeventnews',
 * 				  'eventnews' action.
 * 0.33) Adds the 'uploadposterjson', 'cropposterjson' action.
 * 0.34) Adds the 'event' and 'association' action.
 * 0.35) Adds the 'addnotenews', 'notenews', 'note', 'deletenotenews' action.
 * 0.36) Adds the 'testasidenav' test action.
 * 		 Adds the 'publicnews' action!.
 * 0.37) Adds the 'login', 'logout' and 'resetpassword' action.
 * 0.38) Adds the 'deletepassword' action.
 * 0.39) Renames the 'editphotoset' to 'changephotoset'.
 * 0.40) Adds the 'association', 'profile' and 'photoset' actions related to
 * 				  'showassociationuser', 'showprofileuser' and 'showphotoset' action.
 * 0.41) Adds the 'associationsearchjson', 'associationsearchresulthtml' action.
 *		 Adds the 'profilesearchjson', 'profilesearchresulthtml' action.
 *		 Adds the 'datasearchjson', 'datasearchresulthtml' action.
 *		 Adds the 'eventsearchjson', 'eventsearchresulthtml' action.
 * 0.42) Adds the 'adminpage' and 'register' action.
 * 0.43) Adds the 'error' action.
 * 0.44) Adds the 'weibohtml' action.
 * 0.45) Adds the '' default publicnews action.
 * 0.46) Adds the 'markjson' action.
 * 0.47) Adds the 'about' action.
 * 		 
 */

// XmuSlh only.
if ( !defined('XMUSLH')) { exit(1); }

// Requirements.
require_once INSTALLDIR . '/extlib/Net/URL/Mapper.php';
require_once INSTALLDIR . '/lib/slh_cache.php';

// TODO: What is the meaning?
class XmuSlh_URL_Mapper extends Net_URL_Mapper
{
	private static $_singleton = null;
	private $_actionToPath = array();
	
	private function __construct()
	{
	}
	
	public static function getInstance($id = '__default__')
	{
		if (empty(self::$_singleton)) {
			self::$_singleton = new XmuSlh_URL_Mapper();
		}
		
		return self::$_singleton;
	}
	
	public function connect($path, 
							$defaults = array(), 
							$rules = array())
	{
		$result = null;
		$action = null;
		
		// Ignore the event: 'StartConnectPath'
		$result = parent::connect($path, $defaults, $result);
		if (array_key_exists('action', $defaults)) {
			$actoin = $defaults['action'];
		} else if (array_key_exists('action', $rules)) {
			$action = $rules['action'];
		}
		
		$this->_mapAction($action, $result);
		
		return $result;
	}
	
	protected function _mapAction($action, $path)
	{
		if ( !array_key_exists($action, $this->_actionToPath)) {
			$this->_actionToPath[$action] = array();
		}
		$this->actionToPath[$action][] = $path;
		
		return;
	}
	
	public function generate($values = array(), 
							 $qstring = array(), 
							 $anchor = '')
	{
		if ( !array_key_exists('action', $values)) {
			return parent::generate($values, $qstring, $anchor);
		}
		
		$action = $values['action'];

        if ( !array_key_exists($action, $this->_actionToPath)) {
            return parent::generate($values, $qstring, $anchor);
        }
		
		$oldPaths		= $this->paths;
		$this->paths	= $this->_actionToPath[$action];
		$result			= parent::generate($values, $qstring, $anchor);
		$this->paths	= $oldPaths;
		
		return $result;
	}
}

/**
 * URL Router
 * Cheap wrapper around Net_URL_Mapper.
 * 
 * TODO: The detals:
 */
class Router
{
	var $mapping = null;
	static $inst = null;
	/* static $bare = array('requesttoken', 'accesstoken', 'userauthorization',
                         'postnotice', 'updateprofile', 'finishremotesubscribe'); */

    const REGEX_TAG = '[^\/]+'; // [\pL\pN_\-\.]{1,64} better if we can do unicode regexes
    
    static function get()
    {
    	if ( !Router::$inst) {
    		Router::$inst = new Router();
    	}
    	return Router::$inst;
    }
    
    function __construct()
    {
    	if (empty($this->mapping)) {

    		//if (!common_config('router', 'cache')) {
            //   $this->m = $this->initialize();
            //} else {

				// Use the cache!	
                $key = self::cacheKey();
                $cache = Cache::instance();
                
                $mapping = $cache->get($key);
                
                if (!empty($mapping)) {
                    $this->mapping = $mapping;
                } else {
                    $this->mapping = $this->initialize();
                    $cache->set($key, $this->mapping);
                }
                
            //}
    		
    		// $this->mapping = $this->initialize();
    	}
    }
    
    /**
     * TODO: What is this?
     * 
     * Create a unique hashkey for the router.
     * 
     * The router's url map can change based on the version of the software
     * you're running and the plugins that are enabled. To avoid having bad routes
     * get stuck in the cache, the key includes a list of plugins and the software
     * version.
     */
    static function cacheKey()
    {
    	$parts = array('router');
    	
    	// Many router paths depend on this setting.
    	
    	/*
    	if (common_config('singleuser', 'enabled')) {
            $parts[] = '1user';
        } else {
            $parts[] = 'multi';
        }

        return Cache::codeKey(implode(':', $parts));
    	 */
    	
    	// Use the cache.
    	$parts[] = 'multi';
    	return Cache::codeKey(implode(':', $parts));
    }
    
	/**
     * TODO: Logs and shows the exception.
     * 
     * Parse the url to the array includes action and arguments.
     */
    function map($path)
    {
    	try {
    		$match = $this->mapping->match($path);
    	} catch (Net_URL_Mapper_InvalidException $e) {
			// Logs and shows the exception.
			// TRANS: Client error on action trying to visit a non-existing page.
			/*
            $cac = new ClientErrorAction(_('Page not found.'), 404);
            $cac->showPage();
            */
    	}
    	
    	return $match;
    }
    
    /**
     * TODO: A little stuck.
     * Against the map!!
     * Bulid the router.
     */
    function build($action, 
    			   $args = null, 
    			   $params = null, 
    			   $fragment = null)
    {
    	$action_arg = array('action' => $action);
    	
    	if ($args) {
    		$args = array_merge($action_arg, $args);
    	} else {
    		$args = $action_arg;
    	}
    	
    	$url = $this->mapping->generate($args, $params, $fragment);
    	
    	// Copy from StatusNet below.
    	// Due to a bug in the Net_URL_Mapper code, the returned URL may
        // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
        // repair that here rather than modifying the upstream code...

        $qpos = strpos($url, '?');
        if ($qpos !== false) {
            $url = substr($url, 0, $qpos+1) .
                str_replace('?', '&', substr($url, $qpos+1));

            // @fixme this is a hacky workaround for http_build_query in the
            // lower-level code and bad configs that set the default separator
            // to &amp; instead of &. Encoded &s in parameters will not be
            // affected.
            $url = substr($url, 0, $qpos+1) .
                str_replace('&amp;', '&', substr($url, $qpos+1));

        }

        return $url;
    }
    
    /**
     * TODO: A lot of maps! Hah!
     * 
     * Initialize the mapper.
     */
    function initialize()
    {
    	$mapping = XmuSlh_URL_Mapper::getInstance();
    	
 		// A lot of maps!!!
 		/*********************************************Begin mapping*********************************/
    	
    	/*
    	// 1. One simple test without parameters.
    	$mapping->connect('micronews', array('action' => 'getinfo'));
    	$mapping->connect('info/get/', array('action' => 'getinfo'));
    	
    	// 2. One test with parameter.
    	$mapping->connect('info/:id', array('action' => 'first'), array('id' => '[0-9]+'));
    	
    	// 3. With parameters?
    	$mapping->connect('more/:name/:pass', array('action' => 'getinfo'));
    	
    	$mapping->connect('limit/:format', array('action' => 'getinfo'), 
    									   array('format' => '(xml|json|rss|atom|as)'));
    	*/
    	
    	$mapping->connect('getinfo', array('action'  => 'getinfo'), 
    								 array('catalog' => 'all'));
    	$mapping->connect('getinfo/:catalog', array('action'  => 'getinfo'),
    										  array('catalog' => '[0-9]+'));
    										  
    	// Database
    	$mapping->connect('database', array('action' => 'database'));
    	
    	// Test form
    	$mapping->connect('testform', array('action' => 'testform'));
    	
    	// Publish
    	$mapping->connect('publish', array('action' => 'publish'));
    	
    	// Article
    	// /article/1
    	$mapping->connect('article/:aid', array('action' => 'article'),
    									  array('aid'	 => '[0-9]+'));
    									  
    	// New Article
    	$mapping->connect('newarticle', array('action' => 'newarticle'));
    	
    	// New File
    	$mapping->connect('newfile', array('action' => 'newfile'));
    	
    	// Get File
    	// /getfile/1
    	$mapping->connect('getfile/:fid', array('action' => 'getfile'),
    									  array('fid'	 => '[0-9]+'));
    									  
    	// Getjson
    	$mapping->connect('getjson', array('action' => 'getjson'));
    	
    	// Parsejson
    	$mapping->connect('parsejson', array('action' => 'parsejson'));
    	
    	// Css
    	$mapping->connect('css', array('action' => 'css'));
    	
    	// TestActionFramework
    	$mapping->connect('actionframework', array('action' => 'testactionframework'));
    	
// TODO: Real work /////////////////////////////////////////////////////////////////////////
// #Default
		// Default
		$mapping->connect('', array('action' => 'publicnews'));
// #User
		// Login
    	$mapping->connect('login', array('action' => 'login'));
    	
    	// Logout
    	$mapping->connect('logout', array('action' => 'logout'));
    	
    	// Resetpassword
    	$mapping->connect('resetpassword', array('action' => 'resetpassword'));
    	
    	// Deletepassword
    	$mapping->connect('deletepassword/:guser_id', array('action' => 'deletepassword'),
    												  array('guser_id' => '[0-9]+'));
    	
// #Profileuser
    	// Addprofileuser
    	$mapping->connect('addprofileuser', array('action' => 'addprofileuser'));
    	
    	// Showprofileuser
    	$mapping->connect('profile', array('action' => 'showprofileuser'));
    	
    	// Showprofileuser
    	$mapping->connect('showprofileuser', array('action' => 'showprofileuser'));
    	
    	// Editprofileuser
    	// /editprofileuser/1
    	$mapping->connect('editprofileuser/:guser_id', array('action'	=> 'editprofileuser'),
    									  			   array('guser_id' => '[0-9]+'));
    									  			  
    	// Deleteprofileuser
    	$mapping->connect('deleteprofileuser/:guser_id', array('action'	 => 'deleteprofileuser'),
    													 array('guser_id' => '[0-9]+'));
    													
// #Associationuser
    	// Addassociationuser
    	$mapping->connect('addassociationuser', array('action' => 'addassociationuser'));
    	
    	// Showassociationuser
    	$mapping->connect('association', array('action' => 'showassociationuser'));
    	
    	// Showassociationuser
    	$mapping->connect('showassociationuser', array('action' => 'showassociationuser'));

    	// Editassociationuser
    	// /editassociationuser/1
    	$mapping->connect('editassociationuser/:guser_id', array('action'	=> 'editassociationuser'),
    									  			  	   array('guser_id' => '[0-9]+'));
    									  			  
    	// Deleteassociationuser
    	$mapping->connect('deleteassociationuser/:guser_id', array('action'	  => 'deleteassociationuser'),
    														 array('guser_id' => '[0-9]+'));
    	
    	// Association
		// /association/1		// For the detailed association page.
		$mapping->connect('association/:guser_id', array('action' => 'association'),
												   array('guser_id' => '[0-9]+'));
												   
		// Mark
		// /markjson
		$mapping->connect('markjson', array('action' => 'markjson'));
														   
// #Avatar
		// Editavatar
		$mapping->connect('editavatar/:guser_id', array('action'   => 'editavatar'),
											  	  array('guser_id' => '[0-9]+'));

// #Log in out
		// Login
    	$mapping->connect('login', array('action' => 'login'));
    	
    	// Logout
    	$mapping->connect('logout', array('action' => 'logout'));
    	
    	// Adminpage
    	$mapping->connect('adminpage', array('action' => 'adminpage'));
    	
    	// Register
    	$mapping->connect('register', array('action' => 'register'));
    	
// #Data
		// Adddata
		$mapping->connect('adddata', array('action' => 'adddata'));
		
		// Data
		$mapping->connect('data', array('action' => 'data'));
		
		// Getdata
		// /getdata/1
		$mapping->connect('getdata/:gdata_id', array('action' => 'getdata'),
											   array('gdata_id' => '[0-9]+'));
											   
		// Deletedata
		// /deletedata/1
		$mapping->connect('deletedata/:gfile_id', array('action' => 'deletedata'),
												  array('gfile_id' => '[0-9]+'));
												  
// #Photoset
		// Choosephotoset
		$mapping->connect('choosephotoset', array('action' => 'choosephotoset'));
		
		// Addphoto
		// /addphoto/1
		$mapping->connect('addphoto/:gphotoset_id', array('action' => 'addphoto'),
													array('gphotoset_id' => '[0-9]+'));
		// Deletephoto
		// /deletephoto/1
		$mapping->connect('deletephoto/:gphoto_id', array('action' => 'deletephoto'),
									  			    array('gphoto_id' => '[0-9]+'));
									  			   
		// Photoset
		$mapping->connect('photoset', array('action' => 'showphotoset'));
		
		// Showphotoset
		$mapping->connect('showphotoset', array('action' => 'showphotoset'));
		
		// Deletephotoset
		// /deletephotoset/1
		$mapping->connect('deletephotoset/:gphotoset_id', array('action' => 'deletephotoset'),
														  array('gphotoset_id' => '[0-9]+'));
		
		// Changephotoset
		// /changephotoset/1
		$mapping->connect('changephotoset/:gphotoset_id', array('action' => 'changephotoset'),
														  array('gphotoset_id' => '[0-9]+'));

		// Photoset
		// /photoset/1
		$mapping->connect('photoset/:gphotoset_id', array('action' => 'photoset'),
													array('gphotoset_id' => '[0-9]+'));		

// #PhotoNews
		// Addphotonews
		$mapping->connect('addphotonews', array('action' => 'addphotonews'));
		
		// Photonews
		$mapping->connect('photonews', array('action' => 'photonews'));
		
		// Deletephotonews
		// /deletephotonews/1
		$mapping->connect('deletephotonews/:gnews_id', array('action' => 'deletephotonews'),
													   array('gnews_id' => '[0-9]+'));
		
// #Picture
		// Uplaodpicturejson
		// /uploadpicture
		$mapping->connect('uploadpicture', array('action' => 'uploadpicturejson'));
				
// #Micronews
		// Addmicronews
		$mapping->connect('addmicronews', array('action' => 'addmicronews'));
		
		// Micronews
		$mapping->connect('micronews', array('action' => 'micronews'));
		
		// Deletemicronews
		// /deletemicronews/1
		$mapping->connect('deletemicronews/:gnews_id', array('action' => 'deletemicronews'),
													   array('gnews_id' => '[0-9]+'));
													   
// #Eventnews
		// Addeventnews
		$mapping->connect('addeventnews', array('action' => 'addeventnews'));
		
		// Eventnews
		$mapping->connect('eventnews', array('action' => 'eventnews'));
		
		// Deleteeventnews
		// /deleteeventnews/1
		$mapping->connect('deleteeventnews/:gnews_id', array('action' => 'deleteeventnews'),
													   array('gnews_id' => '[0-9]+'));
													   
		// Event
		// /event/1		// For the detailed page.
		$mapping->connect('event/:gnews_id', array('action' => 'event'),
											 array('gnews_id' => '[0-9]+'));

// #Poster
		// Uplaodposterjson
		// /uploadposter
		$mapping->connect('uploadposter', array('action' => 'uploadposterjson'));
		
		// Cropposterjson
		// /crop/poster
		$mapping->connect('crop/poster', array('action' => 'cropposterjson'));
		
// #NoteNews
		// Addnotenews
		$mapping->connect('addnotenews', array('action' => 'addnotenews'));
		
		// Notenews
		$mapping->connect('notenews', array('action' => 'notenews'));
		
		// Note
		// /note/1		// For the detailed page.
		$mapping->connect('note/:gnews_id', array('action' => 'note'),
											array('gnews_id' => '[0-9]+'));
		
		// Deletenotenews
		// /deletenotenews/1
		$mapping->connect('deletenotenews/:gnews_id', array('action' => 'deletenotenews'),
													   array('gnews_id' => '[0-9]+'));

// #PublicNews for all _News.
		//News
		$mapping->connect('publicnews', array('action' => 'publicnews'));

// #SearchJson for Searchbox
		// Association
		$mapping->connect('associationsearchjson', array('action' => 'associationsearchjson'));
		$mapping->connect('associationsearchresulthtml/:guser_id', array('action' => 'associationsearchresulthtml'),
																   array('guser_id' => '[0-9]+'));
																   
		// Profile
		$mapping->connect('profilesearchjson', array('action' => 'profilesearchjson'));
		$mapping->connect('profilesearchresulthtml/:guser_id', array('action' => 'profilesearchresulthtml'),
															   array('guser_id' => '[0-9]+'));

		// Data
		$mapping->connect('datasearchjson', array('action' => 'datasearchjson'));
		$mapping->connect('datasearchresulthtml/:gfile_id', array('action' => 'datasearchresulthtml'),
															array('gfile_id' => '[0-9]+'));	
		
		// Event
		$mapping->connect('eventsearchjson', array('action' => 'eventsearchjson'));
		$mapping->connect('eventsearchresulthtml/:gnews_id', array('action' => 'eventsearchresulthtml'),
															 array('gnews_id' => '[0-9]+'));

// #Weibo
		// Getweibohtml
		$mapping->connect('getweibohtml', array('action' => 'getweibohtml'));
		
// #About
		// About
		$mapping->connect('about', array('action' => 'about'));

// #Error
		// Clienterror
		// $mapping->connect('clienterror', array('action' => 'clienterror'));		
		// Servererror											
		// $mapping->connect('servererror', array('action' => 'servererror'));													
    	/*********************************************End mapping***********************************/
    	//
    	
		// For test
		$mapping->connect('testrate', array('action' => 'testrate'));
		$mapping->connect('testasidenav', array('action' => 'testasidenav'));
		$mapping->connect('testsearchbox', array('action' => 'testsearchbox'));
		$mapping->connect('testsearchjson', array('action' => 'testsearchjson'));
		$mapping->connect('testsearchresulthtml/:guser_id', array('action' => 'testsearchresulthtml'),
															array('guser_id' => '[0-9]+'));
    	
    	return $mapping;
    }
}

?>